package procsim;

public abstract class Assemble {
    private static final String[] regdir = new String[] {"ax", "bx", "cx", "dx", "sp", "bp", "si", "di"};
    private static final String[] regind = new String[] {"[bx]", "[bp]", "[si]", "[di]", "[bp+di]", "[bp+si]", "[bx+di]", "[bx+si]"};
    public static void preprocess(String[] lines, int addr) throws AssemblingException{
        int codelines=1;
        // prvi prolaz
        for(int i=0; i<lines.length; i++){
            String line=lines[i];
            if (line==null) continue;
            line=line.trim();
            line=line.replaceAll("\t", " ");
            line=line.replaceAll(" +", " ");
            line=line.replace(" +", "+").replace("+ ", "+"); // [bx + si] => [bx+si]
            if(line.contains(";")) line=line.split(";")[0];
            if(line.contains(":")){
                String[] splt=line.split(":", 2);
                line=splt[1].trim();
            }
            if(!line.equals("")) codelines++;
            lines[i]=line;
        }
        // drugi prolaz
        for(int i=0; i<lines.length; i++){
            String line=lines[i];
            String[] tokens=line.split(" ");
            int len = tokens.length;
            if(len>3) throw new AssemblingException("Too many tokens in line "+i);
            if(!tokens[0].toLowerCase().contains("jsr") && !tokens[0].toLowerCase().contains("jmp"))
                continue;
            if(len>2) throw new AssemblingException("Too many tokens in line "+i);
            if(isNumber(tokens[1])) // nije labela
                continue;
            if(matchesArrayIgnoreCase(tokens[1], regind) || equalsArrayIgnoreCase(tokens[1], regind)) // nije labela
                continue;
        }
    }
    public static MSignal[] assemble(String line){
        if (line==null) return null;
        String[] tokens=line.split(" ");
        int len=tokens.length;
        if(len==1 && tokens[0].equals("")) return null;
        // sama obrada opcodova
        try {
            // bezadresne instrukcije
            if (len == 1 && tokens[0].equalsIgnoreCase("nop")) // 00 000 000
                return binaryCode(new int[]{0, 0, 0, 0, 0, 0, 0, 0});
            if (len == 1 && tokens[0].equalsIgnoreCase("rts")) // 00 000 001
                return binaryCode(new int[]{0, 0, 0, 0, 0, 0, 0, 1});
            if (len == 1 && tokens[0].equalsIgnoreCase("rti")) // 00 000 010
                return binaryCode(new int[]{0, 0, 0, 0, 0, 0, 1, 0});
            if (len == 1 && tokens[0].equalsIgnoreCase("inte")) // 00 000 011
                return binaryCode(new int[]{0, 0, 0, 0, 0, 0, 1, 1});
            if (len == 1 && tokens[0].equalsIgnoreCase("intd")) // 00 000 100
                return binaryCode(new int[]{0, 0, 0, 0, 0, 1, 0, 0});
            if (len == 1 && tokens[0].equalsIgnoreCase("trpe")) // 00 000 101
                return binaryCode(new int[]{0, 0, 0, 0, 0, 1, 0, 1});
            if (len == 1 && tokens[0].equalsIgnoreCase("trpd")) // 00 000 110
                return binaryCode(new int[]{0, 0, 0, 0, 0, 1, 1, 0});
            if (len == 1 && tokens[0].equalsIgnoreCase("halt")) // 00 000 111
                return binaryCode(new int[]{0, 0, 0, 0, 0, 1, 1, 1});
            // skokovi
            if (len == 2 && (tokens[0].equalsIgnoreCase("jmp") || tokens[0].equalsIgnoreCase("jsr"))) {
                int[] opcode, ret = new int[24];
                if(tokens[0].equalsIgnoreCase("jmp"))
                    opcode = new int[]{0, 1, 0, 0, 0, 0, 0, 0}; // 010 000 00
                else
                    opcode = new int[]{0, 1, 0, 0, 0, 0, 0, 1}; // 010 000 01
                int[] operand1 = Util.reverseIntArray(Util.int2BinaryArray(Integer.parseInt(tokens[1], 16)>>8, 8));
                int[] operand2 = Util.reverseIntArray(Util.int2BinaryArray(Integer.parseInt(tokens[1], 16)&0xff, 8));
                for(int i=0; i<8; i++) ret[i]=opcode[i];
                for(int i=0; i<8; i++) ret[8+i]=operand1[i];
                for(int i=0; i<8; i++) ret[16+i]=operand2[i];
                return binaryCode(ret);
            }
            if (len == 2 && (tokens[0].equalsIgnoreCase("bnz") || tokens[0].equalsIgnoreCase("int"))) {
                int[] opcode, ret = new int[16];
                if(tokens[0].equalsIgnoreCase("bnz"))
                    opcode = new int[]{0, 1, 1, 0, 0, 0, 0, 0}; // 011 000 00
                else
                    opcode = new int[]{0, 1, 1, 0, 0, 0, 0, 1}; // 011 000 01
                int[] operand = Util.reverseIntArray(Util.int2BinaryArray(Integer.parseInt(tokens[1], 16), 8));
                for(int i=0; i<8; i++) ret[i]=opcode[i];
                for(int i=0; i<8; i++) ret[8+i]=operand[i];
                return binaryCode(ret);
            }
            // jednoadresne instrukcije
            if (len == 2) {
                int[] opcode, ret;
                if(tokens[0].equalsIgnoreCase("asr"))
                    opcode = new int[]{1, 0, 0, 0, 0, 0, 0, 0}; // 1 0000000
                else if(equalsArrayIgnoreCase(tokens[0], new String[] {"push", "pop", "inc", "dec", "jmpind"}))
                    opcode = new int[]{1, 1, 1, 1, 1, 1, 1, 1}; // 1 1111111
                else return null;
                int[] operands=getOperand(tokens);
                if (operands==null) return null;
                ret=new int[8+operands.length];
                for(int i=0; i<8; i++) ret[i]=opcode[i];
                for(int i=0; i<operands.length; i++)
                    ret[8+i]=operands[i];
                return binaryCode(ret);
            }
            // dvoadresne instrukcije
            if (len == 3) {
                int[] opcode, ret;
                if(tokens[0].equalsIgnoreCase("movs"))
                    opcode = new int[]{1, 0, 0, 0, 0, 0, 0, 1}; // 1 0000 001
                else if(tokens[0].equalsIgnoreCase("movd"))
                    opcode = new int[]{1, 0, 0, 0, 0, 0, 1, 0}; // 1 0000 010
                else if(tokens[0].equalsIgnoreCase("add"))
                    opcode = new int[]{1, 0, 0, 0, 0, 0, 1, 1}; // 1 0000 011
                else if(tokens[0].equalsIgnoreCase("and"))
                    opcode = new int[]{1, 0, 0, 0, 0, 1, 0, 0}; // 1 0000 100
                else return null;
                if(tokens[1].endsWith(",")) // ukloni zarez
                    tokens[1]=tokens[1].replace(",", "");
                int[] operands=getOperands(tokens);
                if (operands==null) return null;
                ret=new int[8+operands.length];
                for(int i=0; i<8; i++) ret[i]=opcode[i];
                for(int i=0; i<operands.length; i++)
                    ret[8+i]=operands[i];
                return binaryCode(ret);
            }
            // greska najverovatnije u kodu operacije
            return null;
        } catch (Exception e) {
            // greska najverovatnije do parsovanja heksa cifara
            return null;
        }
    }
    private static MSignal[] binaryCode(int[] bits){
        int size=bits.length;
        if(size%8!=0){
            System.err.println("Greska u asembliranju: Broj bitova nije deljiv sa 8!");
            return null;
        }
        if(size>32) size=32;
        int num = size/8;
        int cur = 0;
        MSignal[] ret = new MSignal[num];
        int[] byt;
        for(int i=0; i<num; i++){
            byt = new int[8];
            for(int j=7; j>=0; j--) byt[7-j]=bits[i*8+j];
            ret[cur++] = new MSignal(Util.intArray2SignalArray(byt));
        }
        return ret;
    }
    private static boolean equalsArrayIgnoreCase(String str, String[] arr){
        for(int i=0; i<arr.length; i++){
            if (str.equalsIgnoreCase(arr[i]))
                return true;
        }
        return false;
    }
    private static boolean matchesArrayIgnoreCase(String str, String[] arr){
        for(int i=0; i<arr.length; i++){
            String escaped = arr[i].replace("[", "\\[").replace("]", "\\]").replace("+", "\\+");
            if (str.toLowerCase().matches(escaped.toLowerCase()+"[0-9a-fA-F]+"))
                return true;
        }
        return false;
    }
    private static int[] getOperand(String[] tokens){ // za jednoadresne instrukcije
        // 7..6: AM, 5..3: opcode, 2..0: regs2
        int[] addr, opcode, regs=null, other=null, ret;
        int bits=0;
        boolean rd=false, ri=false, rid=false;
        if(equalsArrayIgnoreCase(tokens[1], regdir)){
            addr = new int[] {0, 0};
            rd=true;
        }
        else if(equalsArrayIgnoreCase(tokens[1], regind)){
            addr = new int[] {0, 1};
            ri=true;
        }
        else if(matchesArrayIgnoreCase(tokens[1], regind)){
            addr = new int[] {1, 0};
            rid=true;
        }
        else addr = new int[] {1, 1};
        if(tokens[0].equalsIgnoreCase("asr"))
            opcode = new int[] {0, 0, 0};
        else if(tokens[0].equalsIgnoreCase("push"))
            opcode = new int[] {0, 0, 0};
        else if(tokens[0].equalsIgnoreCase("pop"))
            opcode = new int[] {0, 0, 1};
        else if(tokens[0].equalsIgnoreCase("inc"))
            opcode = new int[] {0, 1, 0};
        else if(tokens[0].equalsIgnoreCase("dec"))
            opcode = new int[] {0, 1, 1};
        else if(tokens[0].equalsIgnoreCase("jmpind"))
            opcode = new int[] {1, 0, 0};
        else return null;
        if(rd){
            for(int i=0; i<regdir.length; i++)
                if(tokens[1].equalsIgnoreCase(regdir[i]))
                    regs = Util.reverseIntArray(Util.int2BinaryArray(i, 3));
        }
        else if(ri || rid){
            for(int i=0; i<regind.length; i++)
                if(tokens[1].toLowerCase().contains(regind[i].toLowerCase()))
                    regs = Util.reverseIntArray(Util.int2BinaryArray(i, 3));
            if(rid)
                other = Util.int2BinaryArray(Util.hex2Int(tokens[1].split("]")[1], 8), bits=8);
        }
        else{ // ne-registarska adresiranja
            String arg="";
            if(tokens[1].startsWith("#")){
                arg=tokens[1].replace("#", "");
                regs = new int[] {0, 1, 1};
                bits=16;
            }
            else if (tokens[1].matches("\\[(.+?)\\]")){
                arg=tokens[1].replace("[", "").replace("]", "");
                regs = new int[] {0, 0, 1};
                bits=16;
            }
            else if(tokens[1].toLowerCase().startsWith("[pc]")){
                arg=tokens[1].toLowerCase().replace("[pc]", "");
                regs = new int[] {0, 1, 0};
                bits=8;
            }
            else if(tokens[1].matches("[0-9a-fA-F]+")){
                arg=tokens[1];
                regs = new int[] {0, 0, 0};
                bits=16;
            } else return null;
            other = Util.int2BinaryArray(Util.hex2Int(arg, 16), bits);
        }
        ret = new int[8+bits];
        for(int i=0; i<2; i++) ret[i]=addr[i];
        for(int i=0; i<3; i++) ret[2+i]=opcode[i];
        for(int i=0; i<3; i++) ret[5+i]=regs[i];
        if (other!=null) for(int i=0; i<bits; i++) ret[8+i]=other[bits-1-i];
        return ret;
    }
    private static int[] getOperands(String[] tokens){ // za dvoadresne instrukcije
        // 7..6: AM, 5..3: regs1, 2..0: regs2
        int[] addr, regs1=null, regs2=null, other=null, ret;
        int bits=0;
        boolean rd=false, ri=false, rid=false;
        if(equalsArrayIgnoreCase(tokens[2], regdir)){
            addr = new int[] {0, 0};
            rd=true;
        }
        else if(equalsArrayIgnoreCase(tokens[2], regind)){
            addr = new int[] {0, 1};
            ri=true;
        }
        else if(matchesArrayIgnoreCase(tokens[2], regind)){
            addr = new int[] {1, 0};
            rid=true;
        }
        else addr = new int[] {1, 1};
        for(int i=0; i<regdir.length; i++)
                if(tokens[1].equalsIgnoreCase(regdir[i]))
                    regs1 = Util.reverseIntArray(Util.int2BinaryArray(i, 3));
        if(regs1==null) return null;
        if(rd){
            for(int i=0; i<regdir.length; i++)
                if(tokens[2].equalsIgnoreCase(regdir[i]))
                    regs2 = Util.reverseIntArray(Util.int2BinaryArray(i, 3));
        }
        else if(ri || rid){
            for(int i=0; i<regind.length; i++)
                if(tokens[2].toLowerCase().contains(regind[i].toLowerCase()))
                    regs2 = Util.reverseIntArray(Util.int2BinaryArray(i, 3));
            if(rid)
                other = Util.int2BinaryArray(Util.hex2Int(tokens[2].split("]")[1], 8), bits=8);
        }
        else{ // ne-registarska adresiranja
            String arg="";
            if(tokens[2].startsWith("#")){
                arg=tokens[2].replace("#", "");
                regs2 = new int[] {0, 1, 1};
                bits=16;
            }
            else if (tokens[2].matches("\\[(.+?)\\]")){
                arg=tokens[2].replace("[", "").replace("]", "");
                regs2 = new int[] {0, 0, 1};
                bits=16;
            }
            else if(tokens[2].toLowerCase().startsWith("[pc]")){
                arg=tokens[2].toLowerCase().replace("[pc]", "");
                regs2 = new int[] {0, 1, 0};
                bits=8;
            }
            else if(tokens[2].matches("[0-9a-fA-F]+")){
                arg=tokens[2];
                regs2 = new int[] {0, 0, 0};
                bits=16;
            } else return null;
            other = Util.int2BinaryArray(Util.hex2Int(arg, 16), bits);
        }
        ret = new int[8+bits];
        for(int i=0; i<2; i++) ret[i]=addr[i];
        for(int i=0; i<3; i++) ret[2+i]=regs1[i];
        for(int i=0; i<3; i++) ret[5+i]=regs2[i];
        if (other!=null) for(int i=0; i<bits; i++) ret[8+i]=other[bits-1-i];
        return ret;
    }
    private static boolean isNumber(String num){
        if (num.matches("[0-9a-fA-F]+"))
            return true;
        else return false;
    }
}
